package com.open.rbac.config.security;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import com.open.center.jwt.provider.JwtProvider;
import com.open.rbac.config.properties.AdminProperties;
import com.open.rbac.config.security.filter.JwtAuthenticationFilter;
import com.open.rbac.config.security.handler.MeAccessDeniedHandler;
import com.open.rbac.config.security.handler.MeAuthenticationEntryPoint;

/**
 * 安全配置
 *
 * @author Riche's
 * @since 2022/8/15
 */
@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    private final MeAuthorizationManager meAuthorizationManager;

    private final JwtProvider jwtProvider;

    private final MeAccessDeniedHandler meAccessDeniedHandler;

    private final MeAuthenticationEntryPoint meAuthenticationEntryPoint;

    private final AdminProperties adminProperties;

    /**
     * DocumentationType.SWAGGER_2 -> /v2/api-docs/**
     * DocumentationType.OAS_30 -> /v3/api-docs/**
     * Unable to render this definition
     * The provided definition does not specify a valid version field.
     */
    private final String[] swaggerUrls = new String[]{
            "/favicon.ico",
            "/v2/api-docs/**",
            "/v3/api-docs/**",
            "/swagger-ui/**",
            "/swagger-resources/**",
            "/error",
            "/actuator/**"
    };

    private final String[] permitUrls = new String[]{
            "/v1/login",
            "/v1/verify_token"
    };

    // 公开的文章不要进行认证，请求不再经过 JwtFilter
    private final String[] publicUrls = new String[]{
            "/v1/post/public/**"
    };


    /**
     * 更底层的配置
     * 处理静态资源和完全忽略的请求
     * 配置HTTP防火墙
     * 不经过Spring Security过滤器链
     *
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> {
            log.info("启用 Customizer：{}", web.getClass().getSimpleName());
            web.httpFirewall(httpFirewall());
        };
    }


    /**
     * 配置HTTP防火墙
     */
    public HttpFirewall httpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();

        // 配置允许的HTTP方法
        firewall.setAllowedHttpMethods(adminProperties.getAllowedHttpMethods());

        // 配置允许的HTTP头
//        firewall.setAllowedHeaderNames(header -> {
//            List<String> allowedHeaders = Arrays.asList(
//                    "Authorization",
//                    "Content-Type",
//                    "X-Requested-With",
//                    "Accept",
//                    "Origin",
//                    "Access-Control-Request-Method",
//                    "Access-Control-Request-Headers"
//            );
//            return allowedHeaders.contains(header);
//        });


        // 配置允许的HTTP参数
        firewall.setAllowedParameterNames(parameter -> {
            List<String> allowedParameters = Arrays.asList(
                    "id",
                    "parentId",
                    "name",
                    "username",
                    "really",
                    "number",
                    "enable"
            );
            return allowedParameters.contains(parameter);
        });

        // 配置URL编码规则
        firewall.setAllowUrlEncodedSlash(true);
        firewall.setAllowUrlEncodedPercent(true);
        firewall.setAllowUrlEncodedPeriod(true);
        return firewall;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                // 配置跨域访问
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                // API方式认证(弹窗登录), Authorization Basic Base64(password)
                .httpBasic(AbstractHttpConfigurer::disable)
                //.httpBasic(Customizer.withDefaults())
                .formLogin(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                // 指定的请求必须经过SSL；
                // 若注释该行 则 http 也支持
                // 因为在{@link com.open.life.config.SSLConfig }中配置了将http请求重定向至https
                .requiresChannel(channel -> channel.anyRequest().requiresSecure())
                // 配置请求授权
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(swaggerUrls).permitAll()
                        .requestMatchers(permitUrls).permitAll()
                        .requestMatchers(publicUrls).permitAll()
                        .anyRequest().access(meAuthorizationManager)
                )
                // Spring 安全会话策略
                // ALWAYS：始终创建会话
                // IF_REQUIRED：必要时创建
                // NEVER：不创建，但如果存在则使用
                // STATELESS：不创建，无论是否存在都不要使用
                // 使用Jwt令牌时，需指定为 STATELESS
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                // 配置安全头
                .headers(headers -> headers
                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
                )
                // 配置异常处理
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(meAuthenticationEntryPoint)
                        .accessDeniedHandler(meAccessDeniedHandler)
                )
                // 添加 JWT 过滤器
                .addFilterBefore(new JwtAuthenticationFilter(Stream.of(swaggerUrls, permitUrls, publicUrls)
                        .flatMap(Stream::of).toList(), jwtProvider), UsernamePasswordAuthenticationFilter.class);

        log.info("启用 Security：{}", httpSecurity.getClass().getSimpleName());
        return httpSecurity.build();
    }

    /**
     * 认证管理器（登录认证时用）
     *
     * @param configuration 认证配置
     * @return 认证管理器
     * @throws Exception 异常
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置跨域访问
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedMethods(adminProperties.getAllowedHttpMethods());
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
        corsConfiguration.setAllowedOrigins(adminProperties.getAllowedOrigins());
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }


}
